/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.tcshuo.json.node;

import com.tcshuo.json.object.Formatter;
import com.tcshuo.json.object.JavaType;
import com.tcshuo.json.object.ObjectConstructor;
import java.lang.reflect.Type;
import java.text.ParseException;
import java.util.Date;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;
import org.apache.commons.beanutils.MethodUtils;
import org.apache.commons.lang3.StringEscapeUtils;

/**
 *
 * @author tengda
 */
public abstract class Node implements java.io.Serializable {

    public abstract String toJson();

    protected Formatter formatter = null;
    
    protected ObjectConstructor constructor = null;
    

    public abstract Node getChildNode(String index);

   
    public Object toObject() {
        return toObject((Class) null);
    }

    public Object toObject(Type clazz) {
        if (clazz == null) {
            return toObject((JavaType) null);
        }
        return this.toObject(new JavaType(clazz));
    }

    public Object toObject(JavaType type) {
        if (this.getFormatter() != null) {
            return this.getFormatter().format(this);
        }
        return this.toObjectByType(type);
    }

    protected abstract Object toObjectByType(JavaType type);

    public static Node formJson(String json) throws ParseException {
        try {
            json = StringEscapeUtils.unescapeHtml4(json);
            JsContext context = new JsContext();
            SimpleScriptContext scriptContext = context.executeContext("var json = " + json + ";");

            return _parser(scriptContext.getAttribute("json"));

        } catch (ScriptException ex) {
            throw new ParseException(json, ex.getLineNumber());
        }
    }

    private static Node _parser(Object o) {
        if (o == null) {
            return new NullNode();
        }
        if (o instanceof CharSequence) {
            return new TextNode(o.toString());
        }
        if (o instanceof Date) {
            return new TextNode(o.toString());
        }
        if (o instanceof Number) {
            return new NumberNode((Number) o);
        }
        if ("sun.org.mozilla.javascript.internal.NativeObject".equals(o.getClass().getName())) {
            return _parserNativeObject(o);
        }
        if ("sun.org.mozilla.javascript.internal.NativeArray".equals(o.getClass().getName())) {
            return _parserNativeArray(o);
        }
        return new TextNode(o.toString());

    }

    private static ObjectNode _parserNativeObject(Object no) {
        ObjectNode node = new ObjectNode();
        Object[] ids = NativeObject_getIds(no);
        for (Object id : ids) {
            node.pushProperty(id.toString(), _parser(NativeObject_get(no, id)));
        }
        return node;
    }

    private static ArrayNode _parserNativeArray(Object na) {
        ArrayNode node = new ArrayNode();

        for (int i = 0; i < NativeArray_size(na); i++) {
            node.add(_parser(NativeArray_get(na, i)));
        }

        return node;
    }

    private static Object[] NativeObject_getIds(Object object) {
        try {
            return (Object[]) MethodUtils.invokeMethod(object, "getIds", new Object[0]);
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    private static Object NativeObject_get(Object object, Object name) {
        try {
            return MethodUtils.invokeMethod(object, "get", name);
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    private static Integer NativeArray_size(Object object) {
        try {
            return (Integer) MethodUtils.invokeMethod(object, "size", new Object[0]);
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    private static Object NativeArray_get(Object object, int index) {
        try {
            return MethodUtils.invokeMethod(object, "get", index);
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }
     public Formatter getFormatter() {
        return formatter;
    }

    public void setFormatter(Formatter formatter) {
        this.formatter = formatter;
    }

    public ObjectConstructor getConstructor() {
        return constructor;
    }

    public void setConstructor(ObjectConstructor constructor) {
        this.constructor = constructor;
    }
    
    


}
